home *** CD-ROM | disk | FTP | other *** search
/ Cream of the Crop 12 / Cream of the Crop 12 (Part II) / Cream of the Crop 12 (Part II).iso / OS2 / BLT2_205.ZIP / src / blt2cx09.c < prev    next >
Encoding:
C/C++ Source or Header  |  1996-02-25  |  17.9 KB  |  615 lines

  1. /* 
  2.  *
  3.  * blt2cx09.c - 17-Oct-1995 Cornel Huth 
  4.  * This module is called by blt2demo.c
  5.  * REINDEX_XB w/ custom user-sort-compare function
  6.  *
  7.  * This module is similar to blt2cx01.c, except that it uses custom compare
  8.  * function.  It can be noticably slower than the instrinsic Bullet sort
  9.  * functions, but the extended capabilities more than make up for that.
  10.  * The function itself, user10fp(), is located near the end of this source
  11.  * 25-Feb-96:
  12.  */
  13.  
  14. #include "platform.h"
  15.  
  16. #ifdef ON_OS2
  17.    #define INCL_DOSPROCESS // for OS/2 threads
  18.    #include <os2.h>
  19. #endif
  20. #ifdef ON_W95
  21.    #define WIN32_LEAN_AND_MEAN
  22.    #include <windows.h>
  23. #endif
  24.  
  25. #include <stdio.h>
  26. #include <stdlib.h>
  27. #include <time.h>
  28. #include <string.h>
  29.  
  30. #ifdef ON_OS2
  31.    #include "bullet2.h"
  32. #endif
  33. #ifdef ON_W95
  34.    #include "bullet95.h"
  35. #endif
  36. #ifdef ON_DOSX
  37.    #include "bulletx.h"
  38. #endif
  39.  
  40. void cx09BuildFieldList(FIELDDESCTYPE fieldList[]);
  41.  
  42. #ifdef ON_OS2
  43.    LONG reindexRez9=0;  // thread reindex return code
  44.    TID tidReindex9;     // reindex thread ID
  45.    VOID APIENTRY ReindexThread9(ACCESSPACK *AP);
  46. #endif
  47. #ifdef ON_W95
  48.    LONG reindexRez9=0;  // thread reindex return code
  49.    DWORD tidReindex9;   // reindex thread ID
  50.    HANDLE hReindex9;    // reindex thread's handle
  51.    VOID APIENTRY ReindexThread9(ACCESSPACK *AP);
  52. #endif
  53.  
  54.  
  55. LONG APIENTRY User10fp(PVOID fp1, PVOID fp2, long count, ULONG handle);
  56.  
  57. extern CHAR *collateTable;
  58.  
  59.  
  60. int cx09(void) {
  61.  
  62. #pragma pack(1)
  63.  
  64. ACCESSPACK AP;
  65. DOSFILEPACK DFP;
  66. CREATEDATAPACK CDP;
  67. CREATEINDEXPACK CIP;
  68. HANDLEPACK HP;
  69. OPENPACK OP;
  70. QUERYSETPACK QSP;
  71.  
  72. #if defined ON_OS2 || defined ON_W95
  73.    STATINDEXPACK SIP;
  74. #endif
  75.  
  76. struct EmpRecType {
  77.  CHAR tag;              // record tag, init to SPACE, * means deleted
  78.  CHAR empID[9];         // SSN (not 0T string)
  79.  CHAR empLN[16];        // last name
  80.  CHAR empFN[16];        // first name
  81.  CHAR empSalary[10];    // salary "1234567.90"
  82.  CHAR empDept[6];       // department assigned
  83. }; // 58 bytes
  84. struct EmpRecType EmpRec;
  85.  
  86. #pragma pack()
  87.  
  88. time_t startTime, endTime;
  89. int display = 0;                // display results or not flag
  90. int thread = 1;                 // dispatch reindex in a thread flag
  91. int lastNameVar = 0;            // used to construct on-the-fly data record...
  92. int lastFourVar = 0;            // ...that is unique
  93.  
  94. double baseSalary = 30000.50;   // all make at least this
  95. double bonus = 0.00;            // but all get varying bonus amounts
  96.  
  97. LONG rez;                       // return value from Bullet
  98.  
  99. CHAR nameIX3[] = "$CX09.IX3";   // name of index file created
  100. ULONG indexID=0;                // handle of index file
  101. CHAR keyExpression[128];        // key expression string buffer (159 max)
  102. CHAR keyBuffer[68];             // buffer used to store/receive key values
  103.  
  104. CHAR nameData[] = "$CX09.DBF";  // name of data file created
  105. ULONG dataID=0;                 // handle of data file
  106. FIELDDESCTYPE fieldList[5];     // 5 fields used in data record
  107.  
  108. LONG recs2add;          // records to add en-masse
  109. LONG i;                 // counter
  110. CHAR tmpStr[64];        // misc stuff, non-Bullet related
  111.  
  112. #define NEW_XBUFF_SIZE (384*1024)  // use 384KB for reindex buffer workspace
  113.  
  114.  
  115. srand((unsigned)time(0));       // see generator
  116.  
  117. memset(fieldList,0,sizeof(fieldList));  // init unused bytes to 0 (required)
  118. cx09BuildFieldList(fieldList);
  119.  
  120.  
  121. // Use 384K for workspace in reindex module
  122.  
  123. QSP.func = SET_SYSVARS_XB;
  124. QSP.item = REINDEX_BUFFER_SIZE;
  125. QSP.itemValue = NEW_XBUFF_SIZE;
  126. rez = BULLET(&QSP);
  127. if (rez) {
  128.    printf("Failed SET_SYSVARS call.  Err: %li\n",rez);
  129.    goto Abend;
  130. }
  131. printf("Bullet reindex tmp buffer was %ld KB, now %ld KB (0 KB means default, 144KB)\n",
  132.         QSP.itemValue/1024,NEW_XBUFF_SIZE/1024);
  133.  
  134.  
  135. // Delete previous files from any previous run (disregard any error return)
  136.  
  137. DFP.func = DELETE_FILE_DOS;
  138. DFP.filenamePtr = nameData;
  139. rez = BULLET(&DFP);
  140. DFP.filenamePtr = nameIX3;
  141. rez = BULLET(&DFP);
  142.  
  143.  
  144. // Create the data file, a standard DBF (ID=3) as defined in fieldList above.
  145.  
  146. CDP.func = CREATE_DATA_XB;
  147. CDP.filenamePtr = nameData;
  148. CDP.noFields = 5;
  149. CDP.fieldListPtr = fieldList;
  150. CDP.fileID = 0x03;
  151. rez = BULLET(&CDP);
  152. if (rez) {
  153.    printf("Failed data file create.  Err: %li\n",rez);
  154.    goto Abend;
  155. }
  156.  
  157.  
  158. // Open the data file (required before creating an index file for it)
  159.  
  160. OP.func = OPEN_DATA_XB;
  161. OP.filenamePtr = nameData;
  162. OP.asMode = READWRITE | DENYNONE;
  163. rez = BULLET(&OP);
  164. if (rez) {
  165.    printf("Failed data file open.  Err: %li\n",rez);
  166.    goto Abend;
  167. }
  168. dataID = OP.handle;
  169.  
  170. // Set the custom User sort-cmp function #10
  171. // before creating/opening the index that uses it
  172.  
  173. QSP.func = SET_SYSVARS_XB;
  174. QSP.item = 10;                  // user sort function #10
  175. QSP.itemValue = (LONG) &User10fp; // function pointer to custom sort-compare
  176. rez = BULLET(&QSP);
  177. if (rez) {
  178.    printf("Failed SET_SYSVARS #10 call.  Err: %li\n",rez);
  179.    goto Abend;
  180. }
  181.  
  182.  
  183. // Create an index file for the data file opened above.
  184. // This example uses the salary field as the sort (floating-point)
  185.  
  186. strcpy(keyExpression,"SALARY");
  187.  
  188. CIP.func = CREATE_INDEX_XB;
  189. CIP.filenamePtr = nameIX3;
  190. CIP.keyExpPtr = keyExpression;
  191. CIP.xbLink = dataID;            // the handle of the data file
  192.  
  193. // '10' below is user sortFunction 10 (may be 10 to 19)
  194.  
  195. CIP.sortFunction = 10 | DUPS_ALLOWED; // user sort w/duplicates allowed
  196.  
  197. CIP.codePage = CODEPAGE;
  198. CIP.countryCode = CTRYCODE;
  199. CIP.collatePtr = collateTable;  
  200. CIP.nodeSize = 512;             // 512-byte node size (or 1024, 2048 bytes)
  201. rez = BULLET(&CIP);
  202. if (rez) {
  203.    printf("Failed index file create.  Err: %li\n",rez);
  204.    goto Abend;
  205. }
  206.  
  207.  
  208. // Open the index file (what we just created above).
  209. // As with the index-create, the index-open requires the handle of the data
  210. // file which this index file indexes.
  211.  
  212. OP.func = OPEN_INDEX_XB;
  213. OP.filenamePtr = nameIX3;
  214. OP.asMode = READWRITE | DENYNONE;
  215. OP.xbLink = dataID;
  216. rez = BULLET(&OP);
  217. if (rez) {
  218.    printf("Failed index file open.  Err: %li\n",rez);
  219.    goto Abend;
  220. }
  221. indexID = OP.handle;
  222.  
  223.  
  224. printf("Display all data accessed (slower results)? (y/N) ");
  225. gets(tmpStr);
  226. if (*tmpStr=='y') display = 1;
  227.  
  228. #if defined ON_OS2 || defined ON_W95
  229.    printf("  Dispatch REINDEX_XB in a separate thread? (Y/n) ");
  230.    gets(tmpStr);
  231.    if (*tmpStr=='n') thread = 0;
  232. #else
  233.    thread = 0;
  234. #endif
  235.  
  236. printf("  How many records do you want for this test run? ");
  237. gets(tmpStr);
  238. recs2add = atol(tmpStr);
  239. if (recs2add < 1) recs2add = 1;  // would you rather end the test?
  240. if (recs2add > 9999999) recs2add = 1; // why wait around for 10M?  
  241.  
  242.  
  243. // Add the data records, which are created here, on-the-fly, varying enough
  244. // to make unique records.  Not that this matters in this example (it does
  245. // in blt2cx01.c) since the key is SALARY, and it does permit duplicates.
  246.  
  247. EmpRec.tag = ' ';                       // set to not-deleted
  248. strncpy(EmpRec.empID,"465990000",9);    // only changing last 4 in test
  249. strcpy(EmpRec.empLN,"0000LastNameNum"); // only changing first 4 in test
  250. strcpy(EmpRec.empFN,"YourFirstName");   // everyone has this first name!
  251. strncpy(EmpRec.empSalary,"      1.00",10);
  252. strcpy(EmpRec.empDept,"MIS");           // everyone works for MIS!
  253.  
  254. printf("    Adding %ld records...  ",recs2add);
  255. time(&startTime);
  256.  
  257. AP.func = ADD_RECORD_XB;
  258. AP.handle = dataID;
  259. AP.recPtr = &EmpRec;
  260. for (i = 1; i <= recs2add; i++) {
  261.  
  262.    bonus = (float) rand();
  263.    sprintf(tmpStr,"%10.2f",baseSalary+bonus);
  264.    strncpy(EmpRec.empSalary,tmpStr,10);
  265.  
  266.    sprintf(tmpStr,"%4.4i",lastFourVar++);
  267.    strncpy(EmpRec.empID+5,tmpStr,4);
  268.    rez = BULLET(&AP);
  269.    if (rez) {
  270.       printf("Failed while adding record %ld.  Err: %li\n",i,rez);
  271.       goto Abend;
  272.    }
  273.    if (lastFourVar > 9999) {                    // changes every 10,000 adds
  274.       lastFourVar = 0;
  275.       sprintf(tmpStr,"%4.4i",++lastNameVar);
  276.       strncpy(EmpRec.empLN,tmpStr,4);           // update first 4 of empLN
  277.    }
  278. }
  279. time(&endTime);
  280. printf("took %lu secs.\n",(endTime - startTime));
  281.  
  282.  
  283. // Reindex 
  284.  
  285. printf("Reindexing %ld records...  \r",recs2add);
  286. time(&startTime);
  287.  
  288. AP.func = REINDEX_XB;           // this is all there is to reindexing even...
  289. AP.handle = indexID;            // ...million-record databases
  290. AP.keyPtr = keyBuffer;          // if dup key error...(see manual for details)
  291. AP.nextPtr = NULL;              // just this one index file
  292.  
  293. if (thread) {
  294.  
  295. #ifdef ON_OS2
  296.  
  297.    rez = DosCreateThread(&tidReindex9,
  298.                          (PFNTHREAD) &ReindexThread9,
  299.                          (ULONG) &AP,
  300.                          CREATE_READY | STACK_SPARSE,
  301.                          32768);  // can get by with much less stack, even 8KB
  302.    if (rez) {
  303.       printf("Could not start reindex thread.  Err: %lu\n",rez);
  304.       goto Abend;
  305.    }
  306.  
  307.    DosSleep(1);  // wait a bit to let reindex code set progress to non-zero
  308.  
  309.    SIP.func = STAT_INDEX_XB;
  310.    SIP.handle = indexID;
  311.    rez = BULLET(&SIP);
  312.    while (rez==0) {
  313.       DosSleep(100);
  314.       printf("Reindexing %ld records... %3.3lu%%\r",recs2add,SIP.progress);
  315.       rez = BULLET(&SIP);
  316.       if (SIP.progress==0) {
  317.          printf("Reindexing %ld records...  ",recs2add);
  318.          break;
  319.       }
  320.    }
  321.    if (rez)
  322.       printf("\nFailed progress check.  Err: %li\n",rez);
  323.  
  324.    // Can actually get here _before_ the reindex thread fully exits, but
  325.    // won't get here until SIP.progress is back to 0.
  326.  
  327.    time(&endTime);
  328.  
  329.    // It's likely that the thread has exited completely by now, but if
  330.    // it was blocked after setting SIP.progress to 0 (hung up in the cache,
  331.    // etc.), then it's possible to get here well before Bullet has exited.
  332.    // Since calling Bullet while Bullet is busy (for most routines) results
  333.    // in a rc=640 being returned (mutex timeout), just call this to wait
  334.    // for the thread to exit completely.  Note that this has only been seen
  335.    // under Win95 (done, but blocked), but this little routine won't hurt.
  336.    // Ignore any error, which there will be if the thread has indeed exited.
  337.    // Another option is to use a non-0 mutex-timeout value, via SET_SYSVARS_XB.
  338.  
  339.    rez = DosWaitThread(&tidReindex9,DCWW_WAIT);
  340.    // ignore error (such as "invalid thread ID")
  341.  
  342.    DosSleep(1); // allow the reindex thread to exit BULLET() (and set rez)
  343.  
  344.    rez = reindexRez9;      // get return code set by ReindexThread9()
  345.    if (rez) {              // rez is already AP.stat
  346.       printf("Failed reindex.  Err: %li\n",rez);
  347.       goto Abend;
  348.    }
  349.    printf("took %lu secs.\n",(endTime - startTime));
  350.  
  351. #endif
  352. #ifdef ON_W95
  353.  
  354.    hReindex9 = CreateThread(NULL,
  355.                            32768,               // 8KB min.
  356.                            (LPTHREAD_START_ROUTINE) &ReindexThread9,
  357.                            (PACCESSPACK) &AP,
  358.                            0,                   // immediate start
  359.                            &tidReindex9);
  360.                       
  361.  
  362.    if (hReindex9==0) {
  363.       rez = GetLastError();
  364.       printf("Could not start reindex thread.  Err: %lu\n",rez);
  365.       goto Abend;
  366.    }
  367.  
  368.    Sleep(32);   // wait a bit to let reindex code set progress to non-zero
  369.  
  370.    SIP.func = STAT_INDEX_XB;
  371.    SIP.handle = indexID;
  372.    rez = BULLET(&SIP);
  373.    while (rez==0) {
  374.       Sleep(100);
  375.       printf("Reindexing %ld records... %3.3lu%%\r",recs2add,SIP.progress);
  376.       rez = BULLET(&SIP);
  377.       if (SIP.progress==0) {
  378.          printf("Reindexing %ld records...  ",recs2add);
  379.          break;
  380.       }
  381.    }
  382.    if (rez)
  383.       printf("\nFailed progress check.  Err: %li\n",rez);
  384.  
  385.    // Can actually get here _before_ the reindex thread fully exits, but
  386.    // won't get here until SIP.progress is back to 0.
  387.  
  388.    time(&endTime);
  389.  
  390.    // It's likely that the thread has exited completely by now, but if
  391.    // it was blocked after setting SIP.progress to 0 (hung up in the cache,
  392.    // etc.), then it's possible to get here well before Bullet has exited.
  393.    // Since calling Bullet while Bullet is busy (for most routines) results
  394.    // in a rc=640 being returned (mutex timeout), just call this to wait
  395.    // for the thread to exit completely.  Note that this has only been seen
  396.    // under Win95 (done, but blocked), but this little routine won't hurt.
  397.    // Ignore any error, which there will be if the thread has indeed exited.
  398.    // Another option is to use a non-0 mutex-timeout value, via SET_SYSVARS_XB.
  399.    // Error code 640 is documented in BULLET95.H.
  400.  
  401.    rez = WaitForSingleObject(hReindex9,10*1000); // 10 secs plenty of flush time
  402.    // ignore error
  403.  
  404.    Sleep(32);   // allow the reindex thread to exit BULLET() (and set rez)
  405.                 // why 32ms?  Only because OS/2 min std resolution is 32ms
  406.  
  407.    rez = reindexRez9;      // get return code set by ReindexThread9()
  408.    if (rez) {              // rez is already AP.stat
  409.       printf("Failed reindex.  Err: %li\n",rez);
  410.       goto Abend;
  411.    }
  412.    printf("took %lu secs.\n",(endTime - startTime));
  413.  
  414. #endif
  415. #ifdef ON_DOSX
  416.  
  417.    printf(" Threads are not supported on this OS\n");
  418.    goto Abend;
  419.  
  420. #endif
  421.  
  422. }
  423. else {
  424.    rez = BULLET(&AP);
  425.    if (rez)  {
  426.       rez = AP.stat;
  427.       printf("Failed reindex.  Err: %li\n",rez);
  428.       goto Abend;
  429.    }
  430.    time(&endTime);
  431.    printf("Reindexing %ld records...  took %lu secs.\n",
  432.            recs2add,
  433.            (endTime-startTime));
  434. }
  435.  
  436.  
  437. // Get key data
  438.  
  439. memset(keyBuffer,0,sizeof(keyBuffer)); 
  440. printf(" Accessing %ld keys...     ",recs2add);
  441. if (display) printf("\n");
  442. time(&startTime);
  443. AP.func = FIRST_KEY_XB;
  444. AP.handle = indexID;
  445. AP.keyPtr = keyBuffer;
  446. rez = BULLET(&AP);
  447. i=0;
  448. while (rez==0) {
  449.    i++;     
  450.    if (display) printf("%s  %9.9lu\r", keyBuffer, AP.recNo);
  451.    AP.func = NEXT_KEY_XB;
  452.    rez = BULLET(&AP);
  453. };
  454. if (display) printf("\n...");
  455.  
  456. // expected rez is EXB_END_OF_FILE
  457.  
  458. if (rez!=EXB_END_OF_FILE)  {
  459.    printf("Failed KEY access.  Err: %li\n",rez);
  460.    goto Abend;
  461. }
  462. time(&endTime);
  463. printf("took %lu secs. for %ld keys\n",(endTime - startTime),i);
  464.  
  465.  
  466. // Get key and record data
  467.  
  468. printf(" Accessing %ld keys+recs...",recs2add);
  469. if (display) printf("\n");
  470. time(&startTime);
  471. AP.func = GET_FIRST_XB;
  472. AP.handle = indexID;
  473. AP.keyPtr = keyBuffer;
  474. AP.recPtr = &EmpRec;
  475. rez = BULLET(&AP);
  476. i=0;
  477. while (rez==0) {
  478.    i++;
  479.    if (display) 
  480.       printf("%s  %9.9lu   %s\r", keyBuffer, AP.recNo, &EmpRec); // partial show
  481.    AP.func = GET_NEXT_XB;
  482.    rez = BULLET(&AP);
  483. };
  484. if (display) printf("\n...");
  485.  
  486. // expected rez is EXB_END_OF_FILE
  487.  
  488. if (rez!=EXB_END_OF_FILE) {
  489.    printf("Failed GET access.  Err: %li\n",rez);
  490.    goto Abend;
  491. }
  492. time(&endTime);
  493. printf("took %lu secs. for %ld keys & records\n",(endTime - startTime),i);
  494.  
  495.  
  496. // Fatal errors above come straight to here
  497. Abend:
  498.  
  499. // Close files
  500.  
  501. if (indexID) {
  502.    HP.func = CLOSE_INDEX_XB;
  503.    HP.handle = indexID;
  504.    rez = BULLET(&HP);
  505.    if (rez)
  506.       printf("Failed index file close.  Err: %li\n",rez);
  507. }
  508.  
  509. // Unlikely the above could fail, considering how far it has gotten so far!
  510. // But logic says that we want to continue closing other open files...
  511.  
  512. if (dataID) {
  513.    HP.func = CLOSE_DATA_XB;
  514.    HP.handle = dataID;
  515.    rez = BULLET(&HP);
  516.    if (rez)
  517.       printf("Failed data file close.  Err: %li\n",rez);
  518. }
  519.  
  520. return rez;  // module exit
  521. }
  522.  
  523.  
  524. #if defined ON_OS2 || defined ON_W95
  525.  
  526. // -------------------------
  527. // Reindex thread, thread #2
  528.  
  529. void APIENTRY ReindexThread9(ACCESSPACK *AP) {
  530.  
  531.    reindexRez9 = BULLET(AP);
  532.  
  533.    // Reindex is a xaction-list routine and so must check AP.stat for any 
  534.    // error code -- rez as returned from the Bullet call is the list item 
  535.    // that failed.  Since this example has but the single item, rez=1 on 
  536.    // failure, with the error code in AP.stat.
  537.  
  538.    if (reindexRez9) {
  539.       reindexRez9 = AP->stat;
  540.    }
  541. }
  542. #endif
  543.  
  544.  
  545. // ----------------------------
  546. // Custom Sort-Compare Function
  547.  
  548. LONG APIENTRY User10fp(PVOID fp1, PVOID fp2, LONG count, ULONG handle) {
  549.  
  550. // High value for this compare, including a maxed-out enumerator (0xFFFF)
  551. // this must be a 'static' declared since a pointer to it is returned
  552. // by this function for the special case.
  553.  
  554. static CHAR highValuesFP[13]="9999999.99\xFF\xFF";  
  555.  
  556.  
  557. if ((count==0) | (handle==0))
  558.    printf("count or handle is 0\n");    // won't happen
  559.  
  560. // If there is not a Build-Key routine to convert the DBF data field from
  561. // ASCII numbers to a binary IEEE float (or if you don't want to use IEEE
  562. // fp key values), then that conversion can be done right here, in the
  563. // compare.  The 10-digit ASCII numbers (same as from the DBF data record)
  564. // are passed as a string, in the form as shown in highValuesFP.  Also,
  565. // count is passed, though it is already known to be 12 in this example 
  566. // (10+2 bytes of the enumerator since DUPS_ALLOWED).  Handle is not used,
  567. // but could be used to find out if duplicates are allowed if this wasn't
  568. // know prior.
  569.  
  570. // never should only one pointer be null -- either both are or neither
  571.  
  572. if ((fp1!=NULL) & (fp2!=NULL)) {
  573.  
  574.    double tfp1 = atof((char *)fp1);
  575.    double tfp2 = atof((char *)fp2);
  576.    if (tfp1 > tfp2) return 1;
  577.    if (tfp1 < tfp2) return -1;
  578.    return 0;
  579. }
  580. else
  581.    return (ULONG)&highValuesFP[0];
  582. }
  583.  
  584.  
  585. //------------------------------------
  586. // Init field list items for data file
  587.  
  588. void cx09BuildFieldList(FIELDDESCTYPE fieldList[]) {
  589.  
  590. strcpy(fieldList[0].fieldName, "SSN");  // field names must be upper-case
  591. fieldList[0].fieldType = 'C';           // field types must be upper-case
  592. fieldList[0].fieldLen = 9;
  593. fieldList[0].fieldDC = 0;
  594.  
  595. strcpy(fieldList[1].fieldName, "LNAME");
  596. fieldList[1].fieldType = 'C';
  597. fieldList[1].fieldLen = 16;
  598. fieldList[1].fieldDC = 0;
  599.  
  600. strcpy(fieldList[2].fieldName, "FNAME");
  601. fieldList[2].fieldType = 'C';
  602. fieldList[2].fieldLen = 16;
  603. fieldList[2].fieldDC = 0;
  604.  
  605. strcpy(fieldList[3].fieldName, "SALARY");
  606. fieldList[3].fieldType = 'N';
  607. fieldList[3].fieldLen = 10;      // "1234567.90"
  608. fieldList[3].fieldDC = 2;
  609.  
  610. strcpy(fieldList[4].fieldName, "DEPT");
  611. fieldList[4].fieldType = 'C';
  612. fieldList[4].fieldLen = 6;
  613. fieldList[4].fieldDC = 0;
  614. }
  615.